package serial

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/tarm/serial"
)

/*
AT+CDNSORIP=1
AT+CIPHEAD=1
AT+CIPSTART="TCP","git.totant.com","1001"
AT+CIPSEND
*/
//使用AT指令上网
var Debug = false

type OnMsgCallFunc func([]byte)  //消息触发
type OnErrorCallFunc func(error) //错误触发
type OnDefCallFunc func()        //默认触发

type CmdHandle struct {
	regexp *regexp.Regexp
	call   interface{}
}

//tcp上网
type GPRS struct {
	serialFile    io.ReadWriteCloser //串口刘
	onMessageCall OnMsgCallFunc      //消息触发回调
	onCloseCall   OnDefCallFunc      //
	onConnectCall OnDefCallFunc      //当连接时
	onErrorCall   OnErrorCallFunc    //

	cmdHandles      map[string]*CmdHandle //命令处理
	cmdHandlesIndex int                   //索引
	sync.Mutex                            //继承

	transed bool //是否在透传状态

	cmdBuf      []byte      //命令缓冲
	linkLock    *sync.Mutex //连接锁
	triggerLock *sync.Mutex
	Test        int
}

//开启gprs网络
func OpenGPRS(com string, baud int) (*GPRS, error) {
	o := new(GPRS)
	o.cmdHandles = make(map[string]*CmdHandle)
	o.cmdBuf = make([]byte, 0)
	o.linkLock = new(sync.Mutex)
	o.triggerLock = new(sync.Mutex)
	e := o.init(com, baud)
	o.Test = 666
	return o, e
}

//115200
func (this *GPRS) init(com string, baud int) error {
	conf := &serial.Config{
		Name: com,
		Baud: baud,
	}
	var e error
	this.serialFile, e = serial.OpenPort(conf)
	if e != nil {
		return e
	}
	go func() {
		for {
			receive := make([]byte, 256)
			l, e := this.serialFile.Read(receive)
			if e != nil || l <= 0 { //读取错误或无数据
				continue
			}
			data := receive[:l]
			if this.transed { //如果连接成功处理透传数据
				go func() {
					this.Lock()
					defer this.Unlock()
					this.onMessageCall(data)
				}()
			}
			this.handleCmd(data)
			//debug("串口收到数据：", string(data))
			time.Sleep(time.Millisecond * 10)
		}
	}()
	return nil
}

//命令字节流处理，通过分隔符来区分命令
func (this *GPRS) handleCmd(bt []byte) {
	//
	//debug("命令列表", this.cmdHandles)
	this.cmdBuf = append(this.cmdBuf, bt...)
	stopIndex := bytes.Index(this.cmdBuf, []byte{13, 10})
	if stopIndex >= 0 {
		dataBt := this.cmdBuf[:stopIndex]
		backURC := strings.TrimSpace(string(dataBt))
		debug("响应命令：", backURC, "____________________")
		if backURC != "" {
			for checkCmd, node := range this.cmdHandles {
				var ok bool
				var res []string
				if backURC == checkCmd {
					ok = true
				} else if res = node.regexp.FindStringSubmatch(backURC); len(res) > 0 {
					ok = true
				}
				//debug("匹配结果：", checkCmd, res)
				if ok {
					//debug("进入到命令。。。。。。", checkCmd)
					if call, ok := node.call.(func([]byte)); ok {
						go call(dataBt)
					} else if call, ok := node.call.(func(...string)); ok {
						go call(res[1:]...)
					}
					this.triggerLock.Lock()
					defer this.triggerLock.Unlock()
					delete(this.cmdHandles, checkCmd)
				}
			}
		}
		if len(this.cmdBuf) > 0 {
			this.cmdBuf = this.cmdBuf[stopIndex+2:]
			this.handleCmd([]byte{}) //递归处理所有数据
		}
	} else {
		this.cmdBuf = []byte{}
	}

}

//同步命令
//cmd=命令
//check=命令返回校验结果
//call=回调函数
//wait=等待时间
//返回=成功true,失败false
func (this *GPRS) WriteCmd(cmd, check string, call func([]byte), wait time.Duration) bool {
	succ := make(chan []byte)
	if wait == 0 {
		wait = time.Second * 2
	}
	this.WriteCmdSync(cmd, check, func(bt []byte) {
		succ <- bt
	})
	ret := false
	select {
	case bt := <-succ: //
		if call != nil {
			call(bt)
		}
		debug("命令：", cmd, "成功", "____________________")
		ret = true
	case <-time.After(wait):
		debug("命令：", cmd, "超时")
	}
	return ret
}

//返回命令，返回str
func (this *GPRS) WriteCmdStr(cmd, check string, call func(...string), wait time.Duration) bool {
	succ := make(chan []string)
	if wait == 0 {
		wait = time.Second * 2
	}
	this.WriteCmdSync(cmd, check, func(arg ...string) {
		succ <- arg
	})
	ret := false
	select {
	case arg := <-succ: //
		if call != nil {
			call(arg...)
		}
		debug("命令：", cmd, "成功", "____________________")
		ret = true
	case <-time.After(wait):
		debug("命令：", cmd, "超时")
	}
	return ret
}

//异步命令
//cmd=命令
//check=命令返回校验结果
//call=回调函数
func (this *GPRS) WriteCmdSync(cmd, check string, call interface{}) {
	this.CmdTrigger(check, call)
	this.Write([]byte(cmd + "\r\n"))
	time.Sleep(time.Millisecond * 10)
}

//发送数据，如果数据很多的时候，分段发送
func (this *GPRS) Write(data []byte) (int, error) {
	this.Lock()
	defer this.Unlock()
	//debug("串口发送数据:", strings.TrimSpace(string(data)), "\n____________________")
	return this.serialFile.Write(data)
}

//命令触发
func (this *GPRS) CmdTrigger(cmd string, call interface{}) {
	this.triggerLock.Lock()
	defer this.triggerLock.Unlock()
	rege, _ := regexp.Compile(cmd)
	this.cmdHandles[cmd] = &CmdHandle{regexp: rege, call: call}
}

//当连接成功时回调
func (this *GPRS) OnConnect(call OnDefCallFunc) {
	this.onConnectCall = call
}

//当收到消息时回调
func (this *GPRS) OnMessage(call OnMsgCallFunc) {
	this.onMessageCall = call
}

//当关闭时触发
func (this *GPRS) OnClose(call OnDefCallFunc) {
	this.onCloseCall = call
}

//当出现异常时触发
func (this *GPRS) OnError(call OnErrorCallFunc) {
	this.onErrorCall = call
}

//关闭透传
func (this *GPRS) CloseTrans() {
	time.Sleep(time.Second * 3)
	this.Write([]byte("+++"))
	time.Sleep(time.Second * 3)
	this.transed = false
	this.WriteCmd("AT+CIPSHUT", "SHUT OK", nil, 0)
}

//是否在透传
func (this *GPRS) IsTrans() bool {
	return this.transed
}

//初始化网络
func (this *GPRS) OpenTrans(host string, port string) error {
	debug("at正在发起连接____________________")
	this.linkLock.Lock()
	defer this.linkLock.Unlock()
	delete(this.cmdHandles, "CLOSED")
	this.CloseTrans() //关闭透传先
	this.WriteCmd("AT+CIPMUX=0", "OK", nil, 0)
	this.WriteCmd("ATE0", "OK", nil, 0)         //设置回显
	this.WriteCmd("AT+CIPMODE=1", "OK", nil, 0) //设置为透传模式
	this.WriteCmd("AT+CIPSTART=\"TCP\",\""+host+"\",\""+port+"\"", "CONNECT", func(bt []byte) {
		this.transed = true
		if this.onConnectCall != nil {
			go this.onConnectCall()
		}
	}, time.Second*5)
	if !this.transed { //超过5秒没有连接成功则重新发起连接
		e := errors.New("连接失败____________________")
		if this.onErrorCall != nil {
			this.onErrorCall(e)
		}
		return e
	}
	closeCall := func(bt []byte) {
		this.transed = false
		if this.onCloseCall != nil {
			go this.onCloseCall()
		}
	}
	this.CmdTrigger("CLOSED", closeCall)
	this.CmdTrigger("TCP ERROR", closeCall)
	return nil
}

func debug(arg ...interface{}) {
	if !Debug {
		return
	}
	fmt.Println(append([]interface{}{"[at]"}, arg...)...)
}
